/*
 * @Author: CBH37
 * @Date: 2023-01-26 17:25:30
 * @Description: Nl BuilDer (NBD) 便于生成汇编
 */
#include "nbd.hpp"

// block
std::shared_ptr<Nbd::Block> Nbd::newBlock(std::string blockName) {
    std::shared_ptr<Block> block = std::make_shared<Block>(blockName);
    blocks.push_back(block);

    return block;
}

void Nbd::setBeginBlock(std::shared_ptr<Block> block) {
    if(beginBlock) {
        error("begin block can only have one");
    }

    beginBlock = block;
}

void Nbd::setInsertBlock(std::shared_ptr<Block> block) {
    insertBlock = block;
}


// newxxx
std::shared_ptr<Nbd::Label> Nbd::newLabel(std::string labelName) {
    std::shared_ptr<Label> label = std::make_shared<Label>(labelName + std::to_string(rand())); // 同`Blcok`
    (*insertBlock).addValue(label);

    return label;
}

std::shared_ptr<Nbd::Instr> Nbd::newInstr(std::string mnem, std::vector<std::shared_ptr<Value>> args) {
    return std::make_shared<Instr>(mnem, args);
}

std::shared_ptr<Nbd::Number> Nbd::newNumber(NlcFile::Num number) {
    return std::make_shared<Number>(number);
}

std::shared_ptr<Nbd::String> Nbd::newString(std::string string) {
    return std::make_shared<String>(string);
}

std::shared_ptr<Nbd::LabelName> Nbd::newLabelName(std::string labelName) {
    return std::make_shared<LabelName>(labelName);
}

template<typename... T>
void Nbd::insertInstr(std::string mnem, T... args) {
    (*insertBlock).addValue(newInstr(mnem, { args... }));
}


// newImstrxxx
void Nbd::newInstrLoadLocal(std::string localName) {
    insertInstr("LOAD_LOCAL", newString(localName));
}

void Nbd::newInstrLoadGlobal(std::string globalName) {
    insertInstr("LOAD_GLOBAL", newString(globalName));
}

void Nbd::newInstrLoadNum(NlcFile::Num num) {
    insertInstr("LOAD_NUM", newNumber(num));
}

void Nbd::newInstrLoadString(std::string string) {
    insertInstr("LOAD_STRING", newString(string));
}

void Nbd::newInstrLoadAddr(std::shared_ptr<Block> block) {
    insertInstr("LOAD_ADDR", newLabelName(block -> getName()));
}

void Nbd::newInstrStoreLocal(std::string localName) {
    insertInstr("STORE_LOCAL", newString(localName));
}

void Nbd::newInstrStoreGlobal(std::string globalName) {
    insertInstr("STORE_GLOBAL", newString(globalName));
}

void Nbd::newInstrAdd() {
    insertInstr("ADD");
}

void Nbd::newInstrSub() {
    insertInstr("SUB");
}

void Nbd::newInstrMul() {
    insertInstr("MUL");
}

void Nbd::newInstrDiv() {
    insertInstr("DIV");
}

void Nbd::newInstrMod() {
    insertInstr("MOD");
}

void Nbd::newInstrPow() {
    insertInstr("POW");
}

void Nbd::newInstrNot() {
    insertInstr("NOT");
}

void Nbd::newInstrCompare(std::string op) {
    newInstrLoadString(op);
    insertInstr("COMPARE");
}

void Nbd::newInstrJmp(std::shared_ptr<Label> label) {
    insertInstr("JMP", newLabelName(label -> getName()));
}

void Nbd::newInstrJmpc(std::shared_ptr<Label> label) {
    insertInstr("JMPC", newLabelName(label -> getName()));
}

void Nbd::newInstrCall() {
    insertInstr("CALL");
}

void Nbd::newInstrCalle() {
    insertInstr("CALLE");
}

void Nbd::newInstrRet() {
    insertInstr("RET");
}

void Nbd::newInstrMakeList() {
    insertInstr("MAKE_LIST");
}

void Nbd::newInstrActionList(std::string actionName) {
    newInstrLoadString(actionName);
    insertInstr("ACTION_LIST");
}

void Nbd::newInstrMakeMap() {
    insertInstr("MAKE_MAP");
}

void Nbd::newInstrActionMap(std::string actionName) {
    newInstrLoadString(actionName);
    insertInstr("ACTION_MAP");
}

void Nbd::newInstrPopTop() {
    insertInstr("POP_TOP");
}

void Nbd::newInstrImport() {
    insertInstr("IMPORT");
}

void Nbd::newInstrExit() {
    insertInstr("EXIT");
}

void Nbd::newInstrNop() {
    insertInstr("NOP");
}


// codegen
std::string Nbd::Number::codegen() {
    return std::to_string(number);
}

std::string Nbd::String::codegen() {
    return "\"" + string + "\"";
}

std::string Nbd::LabelName::codegen() {
    return "$" + name;
}

std::string Nbd::Label::codegen() {
    return name + ":\n";
}

std::string Nbd::Instr::codegen() {
    std::string target = mnem;
    for(auto arg : args) {
        target += " " + arg -> codegen();
    }

    return target;
}

std::string Nbd::Block::codegen() {
    std::string target = name + ":\n";
    for(auto value : values) {
        target += "\t" + value -> codegen() + "\n";
    }

    return target;
}

std::string Nbd::codegen() {
    std::string target = "";

    for(auto block : blocks) {
        if(block == beginBlock) {
            target = block -> codegen() + "\tEXIT\n" + target;
        } else {
            target += block -> codegen();
        }
    }

    return target;
}